using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Net;
using System.Text;
using System.Xml;
using iPhOwa.Exchange.ContentClasses;
using iPhOwa.Exchange.ContentClasses.Fields;
using iPhOwa.Exchange.Exceptions;

namespace iPhOwa.Exchange.WebDav.Methods
{
	/// <summary>
	/// WebDAVS Search Method
	/// </summary>
	internal sealed class Search : Method
	{
		private static readonly string[] requiredFields = new string[]
				{
						ItemFields.ContentClass
				};

		private readonly ICollection<OrderBy> orderBys = new Collection<OrderBy>();

		private readonly ICollection<string> properties;
		private readonly ICollection<Where> wheres = new Collection<Where>();
		private string contentClass;
		private bool isFolder;
		private Range range;

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="path">The exchange store path to search.</param>
		/// <param name="properties">List of properties to retrieve.</param>
		/// <param name="session">Authentication details for this session.</param>
		public Search(Uri path, OwaSession session, IList<string> properties) : base(path, session)
		{
			this.properties = new Collection<string>(properties);
		}

		/// <summary>
		/// Content class to narrow search to.
		/// </summary>
		public string ContentClass
		{
			get { return contentClass; }
			set { contentClass = value; }
		}

		/// <summary>
		/// List of properties to retrieve.
		/// </summary>
		public ICollection<string> Properties
		{
			get
			{
				List<string> props = new List<string>(requiredFields);
				props.AddRange(properties);

				return props;
			}
		}

		/// <summary>
		/// Range of items to limit results to.
		/// </summary>
		public Range Range
		{
			get { return range; }
			set { range = value; }
		}

		/// <summary>
		/// The name of the method
		/// </summary>
		public override string Name
		{
			get { return "SEARCH"; }
		}

		/// <summary>
		/// Sets the isfolder values.
		/// </summary>
		public bool IsFolder
		{
			get { return isFolder; }
			set { isFolder = value; }
		}

		/// <summary>
		/// The order by conditions for the search
		/// </summary>
		public ICollection<OrderBy> OrderBys
		{
			get { return orderBys; }
		}

		/// <summary>
		/// The where clause.
		/// </summary>
		public ICollection<Where> Wheres
		{
			get { return wheres; }
		}

		/// <summary>
		/// Build the command XML.
		/// </summary>
		protected override string BuildCommand()
		{
			string query = string.Format(CultureInfo.InvariantCulture
			                             , @"SELECT {2}
											FROM SCOPE('SHALLOW TRAVERSAL OF ""{0}""')
											WHERE
												""DAV:ishidden"" = false
												AND ""DAV:isfolder"" = {3}
												{1}
												{4}", Path, BuildWhere(), BuildPropertyList(), IsFolder, BuildOrderBy());

			using (EncodingStringWriter s = new EncodingStringWriter(Encoding.UTF8, CultureInfo.InvariantCulture))
			{
				using(XmlTextWriter x = new XmlTextWriter(s))
				{
					x.Formatting = Formatting.Indented;
					x.WriteStartDocument();
					x.WriteStartElement("searchrequest", "DAV:");
					x.WriteElementString("sql", "DAV:", query.Trim());
					x.WriteEndElement();
					x.WriteEndDocument();
				}

				StringBuilder sb = s.GetStringBuilder();

				return sb.ToString();
			}
		}

		private string BuildOrderBy()
		{
			string orderBy = string.Empty;

			if(OrderBys.Count > 0)
			{
				orderBy = "ORDER BY";

				bool and = false;

				foreach(OrderBy ob in OrderBys)
				{
					orderBy += string.Format(CultureInfo.InvariantCulture, @" {0} ""{1}"" {2}"
					                         , and ? "AND" : string.Empty
					                         , ob.PropertyName
					                         , ob.SortDirection == SortDirection.Ascending ? "ASC" : "DESC");

					and = true;
				}
			}

			return orderBy;
		}

		private string BuildWhere()
		{
			string where = string.Empty;

			if(!string.IsNullOrEmpty(ContentClass))
			{
				where += string.Format(CultureInfo.InvariantCulture, @" AND ""DAV:contentclass"" = '{0}' ", ContentClass);
			}

			foreach(Where w in Wheres)
			{
				string value;

				if(w.Value is DateTime)
				{
					value = ((DateTime)w.Value).ToUniversalTime().ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture);
				}
				else
				{
					value = w.Value.ToString();
				}

				where += string.Format(CultureInfo.InvariantCulture, @" AND ""{0}"" {1} '{2}' ", w.PropertyName
				                       , EnumUtility.GetStringValue(w.ConditionalOperator), value);
			}

			return where;
		}

		private string BuildPropertyList()
		{
			List<string> property = new List<string>();

			foreach(string attribute in Properties)
			{
				property.Add(string.Concat("\"", attribute, "\""));
			}

			return string.Join(",", property.ToArray());
		}

		/// <summary>
		/// Add method specific headers to request.
		/// </summary>
		protected override void AddHeaders(HttpWebRequest request)
		{
			if(Range != null)
			{
				request.AddRange("rows", range.From, range.To);
			}

			request.Headers.Add("Depth", 0.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Run the search.
		/// </summary>
		public Collection<Item> Run()
		{
			XmlDocument document = ReadXml(Execute().GetResponseStream());

			Collection<Item> items = new Collection<Item>();

			// HACK: shouldn't assume a:
			XmlNodeList responses = document.GetElementsByTagName("a:response");

			foreach(XmlNode response in responses)
			{
				PropertyDictionary propertyHash = ReadProperties(response.ChildNodes);

				try
				{
					Item item = ItemFactory.Create(propertyHash[ItemFields.ContentClass]);
					item.Load(propertyHash);

					items.Add(item);
				}
				catch(UnsupportedContentClassException)
				{
					// Ignoring unsuported content classes...
				}
			}

			return items;
		}
	}
}